// Project64 - A Nintendo 64 emulator
// https://www.pj64-emu.com/
// Copyright(C) 2001-2021 Project64
// Copyright(C) 2003-2009  Sergey 'Gonetz' Lipski
// Copyright(C) 2007 Hiroshi Morii
// Copyright(C) 2003 Rice1964
// GNU/GPLv2 licensed: https://gnu.org/licenses/gpl-2.0.html

#ifdef _WIN32
#pragma warning(disable: 4786)
#endif

// Dump processed high resolution textures to disk
// (0:disable, 1:enable)
#define DUMP_CACHE 1

// Handle oversized textures by:
// 0: Minification
// 1: Glide64 style tiling
#define TEXTURE_TILING 1

// Use power of 2 texture size
// (0:disable, 1:enable, 2:3dfx)
#define POW2_TEXTURES 2

#if TEXTURE_TILING
#undef POW2_TEXTURES
#define POW2_TEXTURES 2
#endif

// Hack to reduce texture footprint to achieve
// better performance on mid-range graphics cards.
// (0:disable, 1:enable)
#define REDUCE_TEXTURE_FOOTPRINT 0

// Use aggressive format assumption for quantization
// (0:disable, 1:enable, 2:extreme)
#define AGGRESSIVE_QUANTIZATION 1

#include "TxHiResCache.h"
#include "TxDbg.h"
#include <zlib/zlib.h>
#include <string>
#include <Common/path.h>
#include <Common/StdString.h>
#include <Project64-video/Renderer/types.h>
#ifdef _WIN32
#include <io.h>
#endif

TxHiResCache::~TxHiResCache()
{
#if DUMP_CACHE
    if ((_options & DUMP_HIRESTEXCACHE) && !_haveCache && !_abortLoad)
    {
        // Dump cache to disk
        std::string filename = _ident + "_HIRESTEXTURES.dat";

        CPath cachepath(_path.c_str(), "");
        cachepath.AppendDirectory("Cache");
        int config = _options & (HIRESTEXTURES_MASK | COMPRESS_HIRESTEX | COMPRESSION_MASK | TILE_HIRESTEX | FORCE16BPP_HIRESTEX | GZ_HIRESTEXCACHE | LET_TEXARTISTS_FLY);

        TxCache::save(cachepath, filename.c_str(), config);
    }
#endif

    delete _txImage;
    delete _txQuantize;
    delete _txReSample;
}

TxHiResCache::TxHiResCache(int maxwidth, int maxheight, int maxbpp, int options, const char *path, const char *ident, dispInfoFuncExt callback) :
    TxCache((options & ~GZ_TEXCACHE), 0, path, ident, callback)
{
    _txImage = new TxImage();
    _txQuantize = new TxQuantize();
    _txReSample = new TxReSample();

    _maxwidth = maxwidth;
    _maxheight = maxheight;
    _maxbpp = maxbpp;
    _abortLoad = 0;
    _haveCache = 0;

    // Assert local options
    if (!(_options & COMPRESS_HIRESTEX))
    {
        _options &= ~COMPRESSION_MASK;
    }
    if (_path.empty() || _ident.empty())
    {
        _options &= ~DUMP_HIRESTEXCACHE;
        return;
    }

#if DUMP_CACHE
    // Read in high resolution texture cache
    if (_options & DUMP_HIRESTEXCACHE)
    {
        // Find it on disk
        std::string filename = _ident + "_HIRESTEXTURES.dat";
        CPath cachepath(_path.c_str(), "");
        cachepath.AppendDirectory("Cache");
        int config = _options & (HIRESTEXTURES_MASK | COMPRESS_HIRESTEX | COMPRESSION_MASK | TILE_HIRESTEX | FORCE16BPP_HIRESTEX | GZ_HIRESTEXCACHE | LET_TEXARTISTS_FLY);

        _haveCache = TxCache::load(cachepath, filename.c_str(), config);
    }
#endif

    // Read in high resolution textures
    if (!_haveCache) TxHiResCache::load(0);
}

bool
TxHiResCache::empty()
{
    return _cache.empty();
}

bool
TxHiResCache::load(bool replace) // 0: Reload, 1: Replace partial
{
    if (!_path.empty() && !_ident.empty())
    {
        if (!replace) TxCache::clear();

        CPath dir_path(_path.c_str(), "");

        switch (_options & HIRESTEXTURES_MASK)
        {
        case RICE_HIRESTEXTURES:
            INFO(80, "-----\n");
            INFO(80, "Using Rice high resolution texture format...\n");
            INFO(80, "  Must be one of the following;\n");
            INFO(80, "    1) *_rgb.png + *_a.png\n");
            INFO(80, "    2) *_all.png\n");
            INFO(80, "    3) *_ciByRGBA.png\n");
            INFO(80, "    4) *_allciByRGBA.png\n");
            INFO(80, "    5) *_ci.bmp\n");
            INFO(80, "  Usage of only 2) and 3) highly recommended!\n");
            INFO(80, "  Folder names must be in US-ASCII characters!\n");

            dir_path.AppendDirectory(_ident.c_str());
            loadHiResTextures(dir_path, replace);
            break;
        }

        return 1;
    }
    return 0;
}

bool TxHiResCache::loadHiResTextures(const char * dir_path, bool replace)
{
#ifdef _WIN32
    DBG_INFO(80, "-----\n");
    DBG_INFO(80, "path: %s\n", stdstr(dir_path).ToUTF16().c_str());

    CPath TextureDir(dir_path, "");

    // Find it on disk
    if (!TextureDir.DirectoryExists())
    {
        INFO(80, "Error: Path not found!\n");
        return 0;
    }

    // Recursive read into sub-directory
    TextureDir.SetNameExtension("*");
    if (TextureDir.FindFirst(CPath::FIND_ATTRIBUTE_SUBDIR))
    {
        do
        {
            loadHiResTextures(TextureDir, replace);
        } while (TextureDir.FindNext());
    }

    TextureDir.SetNameExtension("*.*");
    if (TextureDir.FindFirst())
    {
        do
        {
            if (_abortLoad) break;

            DBG_INFO(80, "-----\n");
            DBG_INFO(80, "file: %ls\n", stdstr(TextureDir.GetNameExtension().c_str()).ToUTF16().c_str());

            int width = 0, height = 0;
            uint16 format = 0;
            uint8 *tex = nullptr;
            int tmpwidth = 0, tmpheight = 0;
            uint16 tmpformat = 0;
            uint8 *tmptex = nullptr;
            int untiled_width = 0, untiled_height = 0;
            uint16 destformat = 0;

            // Rice high resolution textures: begin
            uint32 chksum = 0, fmt = 0, siz = 0, palchksum = 0;
            char *pfname = nullptr, fname[260];
            std::string ident;
            FILE *fp = nullptr;

            strcpy(fname, _ident.c_str());
            // Case sensitivity fiasco!
            // Files must use _a, _rgb, _all, _allciByRGBA, _ciByRGBA, _ci
            // and file extensions must be in lower case letters!
#ifdef _WIN32
            {
                unsigned int i;
                for (i = 0; i < strlen(fname); i++) fname[i] = (char)tolower(fname[i]);
            }
#endif
            ident.assign(fname);

            // Read in Rice's file naming convention
#define CRCFMTSIZ_LEN 13
#define PALCRC_LEN 9
            wcstombs(fname, stdstr(TextureDir.GetNameExtension()).ToUTF16().c_str(), 260);
            // Case sensitivity fiasco!
            // Files must use _a, _rgb, _all, _allciByRGBA, _ciByRGBA, _ci
            // and file extensions must be in lower case letters!
#ifdef _WIN32
            {
                unsigned int i;
                for (i = 0; i < strlen(fname); i++) fname[i] = (char)tolower(fname[i]);
            }
#endif
            pfname = fname + strlen(fname) - 4;
            if (!(pfname == strstr(fname, ".png") ||
                pfname == strstr(fname, ".bmp") ||
                pfname == strstr(fname, ".dds")))
            {
#if !DEBUG
                INFO(80, "-----\n");
                INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                INFO(80, "Error: Not PNG or BMP or DDS!\n");
                continue;
            }
            pfname = strstr(fname, ident.c_str());
            if (pfname != fname) pfname = 0;
            if (pfname) {
                if (sscanf(pfname + ident.size(), "#%08X#%01X#%01X#%08X", &chksum, &fmt, &siz, &palchksum) == 4)
                    pfname += (ident.size() + CRCFMTSIZ_LEN + PALCRC_LEN);
                else if (sscanf(pfname + ident.size(), "#%08X#%01X#%01X", &chksum, &fmt, &siz) == 3)
                    pfname += (ident.size() + CRCFMTSIZ_LEN);
                else
                    pfname = 0;
            }
            if (!pfname) {
#if !DEBUG
                INFO(80, "-----\n");
                INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                INFO(80, "Error: Not Rice texture naming convention!\n");
                continue;
            }
            if (!chksum) {
#if !DEBUG
                INFO(80, "-----\n");
                INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                INFO(80, "Error: CRC32 = 0!\n");
                continue;
            }

            // Check if we already have it in high resolution texture cache
            if (!replace) {
                uint64_t chksum64 = (uint64_t)palchksum;
                chksum64 <<= 32;
                chksum64 |= (uint64_t)chksum;
                if (TxCache::is_cached(chksum64)) {
#if !DEBUG
                    INFO(80, "-----\n");
                    INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                    INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                    INFO(80, "Error: Already cached! Duplicate texture!\n");
                    continue;
                }
            }

            DBG_INFO(80, "ROM: %ls chksum:%08X %08X fmt:%x size:%x\n", _ident.c_str(), chksum, palchksum, fmt, siz);

			/* Deal with the odd way some texture packs utilize the Rice format.
            * Read in the following order: _a.* + _rgb.*, _all.png _ciByRGBA.png,
            * _allciByRGBA.png, and _ci.bmp. PNG are preferred over BMP.
            *
            * For some reason there are texture packs that include them all. Some
            * even have RGB textures named as _all.* and ARGB textures named as
            * _rgb.*. Someone please write a good guideline for the texture
            * designers!
			* 
            * We allow high resolution textures to have higher bpp than the N64 original textures.
            * N64 formats
            * Format: 0 - RGBA, 1 - YUV, 2 - CI, 3 - IA, 4 - I
            * Size:   0 - 4-bit, 1 - 8-bit, 2 - 16-bit, 3 - 32-bit
            */

            // Read in _rgb.* and _a.*
            if (pfname == strstr(fname, "_rgb.") || pfname == strstr(fname, "_a.")) {
                strcpy(pfname, "_rgb.png");
                CPath TargetFile(dir_path, fname);
                if (!TargetFile.Exists())
                {
                    strcpy(pfname, "_rgb.bmp");
                    TargetFile = CPath(dir_path, fname);
                    if (!TargetFile.Exists())
                    {
#if !DEBUG
                        INFO(80, "-----\n");
                        INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                        INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                        INFO(80, "Error: Missing _rgb.*! _a.* must be paired with _rgb.*!\n");
                        continue;
                    }
                }
                // _a.png
                strcpy(pfname, "_a.png");
                TargetFile = CPath(dir_path, fname);
                if ((fp = fopen(TargetFile, "rb")) != nullptr) {
                    tmptex = _txImage->readPNG(fp, &tmpwidth, &tmpheight, &tmpformat);
                    fclose(fp);
                }
                if (!tmptex) {
                    // _a.bmp
                    strcpy(pfname, "_a.bmp");
                    TargetFile = CPath(dir_path, fname);
                    if ((fp = fopen(TargetFile, "rb")) != nullptr) {
                        tmptex = _txImage->readBMP(fp, &tmpwidth, &tmpheight, &tmpformat);
                        fclose(fp);
                    }
                }
                // _rgb.png
                strcpy(pfname, "_rgb.png");
                TargetFile = CPath(dir_path, fname);
                if ((fp = fopen(TargetFile, "rb")) != nullptr) {
                    tex = _txImage->readPNG(fp, &width, &height, &format);
                    fclose(fp);
                }
                if (!tex) {
                    // _rgb.bmp
                    strcpy(pfname, "_rgb.bmp");
                    TargetFile = CPath(dir_path, fname);
                    if ((fp = fopen(TargetFile, "rb")) != nullptr) {
                        tex = _txImage->readBMP(fp, &width, &height, &format);
                        fclose(fp);
                    }
                }
                if (tmptex) {
                    // Check if _rgb.* and _a.* have matching size and format
                    if (!tex || width != tmpwidth || height != tmpheight ||
                        format != GFX_TEXFMT_ARGB_8888 || tmpformat != GFX_TEXFMT_ARGB_8888) {
#if !DEBUG
                        INFO(80, "-----\n");
                        INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                        INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                        if (!tex) {
                            INFO(80, "Error: Missing _rgb.*!\n");
                        }
                        else if (width != tmpwidth || height != tmpheight) {
                            INFO(80, "Error: _rgb.* and _a.* have mismatched width or height!\n");
                        }
                        else if (format != GFX_TEXFMT_ARGB_8888 || tmpformat != GFX_TEXFMT_ARGB_8888) {
                            INFO(80, "Error: _rgb.* or _a.* not in 32-bit color!\n");
                        }
                        if (tex) free(tex);
                        if (tmptex) free(tmptex);
                        tex = nullptr;
                        tmptex = nullptr;
                        continue;
                    }
                }
                // Make adjustments
                if (tex) {
                    if (tmptex) {
                        // Merge (A)RGB and A comp
                        DBG_INFO(80, "Merge (A)RGB and A comp\n");
                        int i;
                        for (i = 0; i < height * width; i++) {
#if 1
                            // Use R comp for alpha. This is what Rice uses.
                            ((uint32*)tex)[i] &= 0x00ffffff;
                            ((uint32*)tex)[i] |= ((((uint32*)tmptex)[i] & 0x00ff0000) << 8);
#endif
#if 0
                            // Use libpng style grayscale conversion
                            uint32 texel = ((uint32*)tmptex)[i];
                            uint32 acomp = (((texel >> 16) & 0xff) * 6969 +
                                ((texel >> 8) & 0xff) * 23434 +
                                ((texel) & 0xff) * 2365) / 32768;
                            ((uint32*)tex)[i] = (acomp << 24) | (((uint32*)tex)[i] & 0x00ffffff);
#endif
#if 0
                            // Use the standard NTSC grayscale conversion
                            uint32 texel = ((uint32*)tmptex)[i];
                            uint32 acomp = (((texel >> 16) & 0xff) * 299 +
                                ((texel >> 8) & 0xff) * 587 +
                                ((texel) & 0xff) * 114) / 1000;
                            ((uint32*)tex)[i] = (acomp << 24) | (((uint32*)tex)[i] & 0x00ffffff);
#endif
                        }
                        free(tmptex);
                        tmptex = nullptr;
                    }
                    else {
                        // Clobber A comp. Never a question of alpha. Only RGB used.
#if !DEBUG
                        INFO(80, "-----\n");
                        INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                        INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                        INFO(80, "Warning: Missing _a.*! Only using _rgb.*. Treat as opaque texture.\n");
                        int i;
                        for (i = 0; i < height * width; i++) {
                            ((uint32*)tex)[i] |= 0xff000000;
                        }
                    }
                }
            }
            else

                // Read in _all.png, _all.dds, _allciByRGBA.png, _allciByRGBA.dds
                // _ciByRGBA.png, _ciByRGBA.dds, _ci.bmp
                if (pfname == strstr(fname, "_all.png") ||
                    pfname == strstr(fname, "_all.dds") ||
#ifdef _WIN32
                    pfname == strstr(fname, "_allcibyrgba.png") ||
                    pfname == strstr(fname, "_allcibyrgba.dds") ||
                    pfname == strstr(fname, "_cibyrgba.png") ||
                    pfname == strstr(fname, "_cibyrgba.dds") ||
#else
                    pfname == strstr(fname, "_allciByRGBA.png") ||
                    pfname == strstr(fname, "_allciByRGBA.dds") ||
                    pfname == strstr(fname, "_ciByRGBA.png") ||
                    pfname == strstr(fname, "_ciByRGBA.dds") ||
#endif
                    pfname == strstr(fname, "_ci.bmp")) {
                    CPath TargetFile(dir_path, fname);
                    if ((fp = fopen(TargetFile, "rb")) != nullptr) {
                        if (strstr(fname, ".png")) tex = _txImage->readPNG(fp, &width, &height, &format);
                        else if (strstr(fname, ".dds")) tex = _txImage->readDDS(fp, &width, &height, &format);
                        else                            tex = _txImage->readBMP(fp, &width, &height, &format);
                        fclose(fp);
                    }
                    // Auto-adjustment of dxt DDS textures unsupported for now
                    if (tex && strstr(fname, ".dds")) {
                        const float aspectratio = (width > height) ? (float)width / (float)height : (float)height / (float)width;
                        if (!(aspectratio == 1.0 ||
                            aspectratio == 2.0 ||
                            aspectratio == 4.0 ||
                            aspectratio == 8.0)) {
                            free(tex);
                            tex = nullptr;
#if !DEBUG
                            INFO(80, "-----\n");
                            INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                            INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                            INFO(80, "Error: W:H aspect ratio range not 8:1 - 1:8!\n");
                            continue;
                        }
                        if (width != _txReSample->nextPow2(width) ||
                            height != _txReSample->nextPow2(height)) {
                            free(tex);
                            tex = nullptr;
#if !DEBUG
                            INFO(80, "-----\n");
                            INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                            INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                            INFO(80, "Error: Not power of 2 size!\n");
                            continue;
                        }
                    }
                }

            // If we do not have a texture at this point then fail
            if (!tex) {
#if !DEBUG
                INFO(80, "-----\n");
                INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                INFO(80, "Error: Load failed!\n");
                continue;
            }
            DBG_INFO(80, "Read in as %d x %d gfmt:%x\n", tmpwidth, tmpheight, tmpformat);

            // Check if size and format are OK
            if (!(format == GFX_TEXFMT_ARGB_8888 ||
                format == GFX_TEXFMT_P_8 ||
                format == GFX_TEXFMT_ARGB_CMP_DXT1 ||
                format == GFX_TEXFMT_ARGB_CMP_DXT3 ||
                format == GFX_TEXFMT_ARGB_CMP_DXT5) ||
                (width * height) < 4) { // TxQuantize requirement: width * height must be 4 or larger.
                free(tex);
                tex = nullptr;
#if !DEBUG
                INFO(80, "-----\n");
                INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                INFO(80, "Error: Not width * height > 4 or 8-bit palette color or 32bpp or dxt1 or dxt3 or dxt5!\n");
                continue;
            }

            // Analyze and determine best format to quantize
            if (format == GFX_TEXFMT_ARGB_8888) {
                int i;
                int alphabits = 0;
                int fullalpha = 0;
                bool intensity = 1;

                if (!(_options & LET_TEXARTISTS_FLY)) {
                    // Hack alert!
                    /* Account for Rice's weirdness with fmt:0 siz:2 textures.
                     * Although the conditions are relaxed with other formats,
                     * the D3D RGBA5551 surface is used for this format in certain
                     * cases. See Nintemod's SuperMario64 life gauge and power
                     * meter. The same goes for fmt:2 textures. See Mollymutt's
                     * PaperMario text. */
                    if ((fmt == 0 && siz == 2) || fmt == 2) {
                        DBG_INFO(80, "Remove black, white, etc. borders along the alpha edges.\n");
                        // Round A comp
                        for (i = 0; i < height * width; i++) {
                            uint32 texel = ((uint32*)tex)[i];
                            ((uint32*)tex)[i] = ((texel & 0xff000000) == 0xff000000 ? 0xff000000 : 0) |
                                (texel & 0x00ffffff);
                        }
                        // Substitute texel color with the average of the surrounding
                        // opaque texels. This removes borders regardless of hardware
                        // texture filtering (bilinear, etc.)
                        int j;
                        for (i = 0; i < height; i++) {
                            for (j = 0; j < width; j++) {
                                uint32 texel = ((uint32*)tex)[i * width + j];
                                if ((texel & 0xff000000) != 0xff000000) {
                                    uint32 tmptexel[8];
                                    uint32 k, numtexel, r, g, b;
                                    numtexel = r = g = b = 0;
                                    memset(&tmptexel, 0, sizeof(tmptexel));
                                    if (i > 0) {
                                        tmptexel[0] = ((uint32*)tex)[(i - 1) * width + j];                        // North
                                        if (j > 0)         tmptexel[1] = ((uint32*)tex)[(i - 1) * width + j - 1]; // Northwest
                                        if (j < width - 1) tmptexel[2] = ((uint32*)tex)[(i - 1) * width + j + 1]; // Northeast
                                    }
                                    if (i < height - 1) {
                                        tmptexel[3] = ((uint32*)tex)[(i + 1) * width + j];                        // South
                                        if (j > 0)         tmptexel[4] = ((uint32*)tex)[(i + 1) * width + j - 1]; // Southwest
                                        if (j < width - 1) tmptexel[5] = ((uint32*)tex)[(i + 1) * width + j + 1]; // Southeast
                                    }
                                    if (j > 0)         tmptexel[6] = ((uint32*)tex)[i * width + j - 1]; // West
                                    if (j < width - 1) tmptexel[7] = ((uint32*)tex)[i * width + j + 1]; // East
                                    for (k = 0; k < 8; k++) {
                                        if ((tmptexel[k] & 0xff000000) == 0xff000000) {
                                            r += ((tmptexel[k] & 0x00ff0000) >> 16);
                                            g += ((tmptexel[k] & 0x0000ff00) >> 8);
                                            b += ((tmptexel[k] & 0x000000ff));
                                            numtexel++;
                                        }
                                    }
                                    if (numtexel) {
                                        ((uint32*)tex)[i * width + j] = ((r / numtexel) << 16) |
                                            ((g / numtexel) << 8) |
                                            ((b / numtexel));
                                    }
                                    else {
                                        ((uint32*)tex)[i * width + j] = texel & 0x00ffffff;
                                    }
                                }
                            }
                        }
                    }
                }

                // Simple analysis of texture
                for (i = 0; i < height * width; i++) {
                    uint32 texel = ((uint32*)tex)[i];
                    if (alphabits != 8) {
#if AGGRESSIVE_QUANTIZATION
                        if ((texel & 0xff000000) < 0x00000003) {
                            alphabits = 1;
                            fullalpha++;
                        }
                        else if ((texel & 0xff000000) < 0xfe000000) {
                            alphabits = 8;
                        }
#else
                        if ((texel & 0xff000000) == 0x00000000) {
                            alphabits = 1;
                            fullalpha++;
                        }
                        else if ((texel & 0xff000000) != 0xff000000) {
                            alphabits = 8;
                        }
#endif
                    }
                    if (intensity) {
                        int rcomp = (texel >> 16) & 0xff;
                        int gcomp = (texel >> 8) & 0xff;
                        int bcomp = (texel) & 0xff;
#if AGGRESSIVE_QUANTIZATION
                        if (abs(rcomp - gcomp) > 8 || abs(rcomp - bcomp) > 8 || abs(gcomp - bcomp) > 8) intensity = 0;
#else
                        if (rcomp != gcomp || rcomp != bcomp || gcomp != bcomp) intensity = 0;
#endif
                    }
                    if (!intensity && alphabits == 8) break;
                }
                DBG_INFO(80, "Required alpha bits:%d zero acomp texels:%d rgb as intensity:%d\n", alphabits, fullalpha, intensity);

                // Preparations based on above analysis
#if !REDUCE_TEXTURE_FOOTPRINT
                if (_maxbpp < 32 || _options & (FORCE16BPP_HIRESTEX | COMPRESSION_MASK)) {
#endif
                    if (alphabits == 0) destformat = GFX_TEXFMT_RGB_565;
                    else if (alphabits == 1) destformat = GFX_TEXFMT_ARGB_1555;
                    else                     destformat = GFX_TEXFMT_ARGB_8888;
#if !REDUCE_TEXTURE_FOOTPRINT
                }
                else {
                    destformat = GFX_TEXFMT_ARGB_8888;
                }
#endif
                if (fmt == 4 && alphabits == 0) {
                    destformat = GFX_TEXFMT_ARGB_8888;
                    // Rice I format; I = (R + G + B) / 3
                    for (i = 0; i < height * width; i++) {
                        uint32 texel = ((uint32*)tex)[i];
                        uint32 icomp = (((texel >> 16) & 0xff) +
                            ((texel >> 8) & 0xff) +
                            ((texel) & 0xff)) / 3;
                        ((uint32*)tex)[i] = (icomp << 24) | (texel & 0x00ffffff);
                    }
                }
                if (intensity) {
                    if (alphabits == 0) {
                        if (fmt == 4) destformat = GFX_TEXFMT_ALPHA_8;
                        else          destformat = GFX_TEXFMT_INTENSITY_8;
                    }
                    else {
                        destformat = GFX_TEXFMT_ALPHA_INTENSITY_88;
                    }
                }

                DBG_INFO(80, "Best gfmt:%x\n", destformat);
            }
            // Rice high resolution textures: end
            // Only ARGB8888 for now, we will come back to this later...
            if (format == GFX_TEXFMT_ARGB_8888) {
#if TEXTURE_TILING
                // Minification
                {
                    int ratio = 1;

                    // Minification to enable Glide64 style texture tiling
                    // Determine the minification ratio to tile the texture into 256x256 size
                    if ((_options & TILE_HIRESTEX) && _maxwidth >= 256 && _maxheight >= 256) {
                        DBG_INFO(80, "Determine minification ratio to tile\n");
                        tmpwidth = width;
                        tmpheight = height;
                        if (height > 256) {
                            ratio = ((height - 1) >> 8) + 1;
                            tmpwidth = width / ratio;
                            tmpheight = height / ratio;
                            DBG_INFO(80, "height > 256, minification ratio:%d %d x %d -> %d x %d\n",
                                ratio, width, height, tmpwidth, tmpheight);
                        }
                        if (tmpwidth > 256 && (((tmpwidth - 1) >> 8) + 1) * tmpheight > 256) {
                            ratio *= ((((((tmpwidth - 1) >> 8) + 1) * tmpheight) - 1) >> 8) + 1;
                            DBG_INFO(80, "width > 256, minification ratio:%d %d x %d -> %d x %d\n",
                                ratio, width, height, width / ratio, height / ratio);
                        }
                    }
                    else {
                        // Normal minification to fit max texture size
                        if (width > _maxwidth || height > _maxheight) {
                            DBG_INFO(80, "Determine minification ratio to fit max texture size\n");
                            tmpwidth = width;
                            tmpheight = height;
                            while (tmpwidth > _maxwidth) {
                                tmpheight >>= 1;
                                tmpwidth >>= 1;
                                ratio <<= 1;
                            }
                            while (tmpheight > _maxheight) {
                                tmpheight >>= 1;
                                tmpwidth >>= 1;
                                ratio <<= 1;
                            }
                            DBG_INFO(80, "Minification ratio:%d %d x %d -> %d x %d\n",
                                ratio, width, height, tmpwidth, tmpheight);
                        }
                    }

                    if (ratio > 1) {
                        if (!_txReSample->minify(&tex, &width, &height, ratio)) {
                            free(tex);
                            tex = nullptr;
                            DBG_INFO(80, "Error: Minification failed!\n");
                            continue;
                        }
                    }
                }

                // Tiling
                if ((_options & TILE_HIRESTEX) && _maxwidth >= 256 && _maxheight >= 256) {
                    bool usetile = 0;

                    // To tile or not to tile, that is the question
                    if (width > 256 && height <= 128 && (((width - 1) >> 8) + 1) * height <= 256) {
                        if (width > _maxwidth) usetile = 1;
                        else {
                            // Tile if the tiled texture memory footprint is smaller
                            int tilewidth = 256;
                            int tileheight = _txReSample->nextPow2((((width - 1) >> 8) + 1) * height);
                            tmpwidth = width;
                            tmpheight = height;

                            // 3DFX Glide3 tmpheight, W:H aspect ratio range (8:1 - 1:8)
                            if (tilewidth > (tileheight << 3)) tileheight = tilewidth >> 3;

                            // HACKALERT: see TxReSample::pow2();
                            if (tmpwidth > 64) tmpwidth -= 4;
                            else if (tmpwidth > 16) tmpwidth -= 2;
                            else if (tmpwidth > 4) tmpwidth -= 1;

                            if (tmpheight > 64) tmpheight -= 4;
                            else if (tmpheight > 16) tmpheight -= 2;
                            else if (tmpheight > 4) tmpheight -= 1;

                            tmpwidth = _txReSample->nextPow2(tmpwidth);
                            tmpheight = _txReSample->nextPow2(tmpheight);

                            // 3DFX Glide3 tmpheight, W:H aspect ratio range (8:1 - 1:8)
                            if (tmpwidth > tmpheight) {
                                if (tmpwidth > (tmpheight << 3)) tmpheight = tmpwidth >> 3;
                            }
                            else {
                                if (tmpheight > (tmpwidth << 3)) tmpwidth = tmpheight >> 3;
                            }

                            usetile = (tilewidth * tileheight < tmpwidth * tmpheight);
                        }
                    }

                    // Tile it! Do the actual tiling into 256x256 size
                    if (usetile) {
                        DBG_INFO(80, "Glide64 style texture tiling\n");

                        int x, y, z, ratio, offset;
                        offset = 0;
                        ratio = ((width - 1) >> 8) + 1;
                        tmptex = (uint8 *)malloc(_txUtil->sizeofTx(256, height * ratio, format));
                        if (tmptex) {
                            for (x = 0; x < ratio; x++) {
                                for (y = 0; y < height; y++) {
                                    if (x < ratio - 1) {
                                        memcpy(&tmptex[offset << 2], &tex[(x * 256 + y * width) << 2], 256 << 2);
                                    }
                                    else {
                                        for (z = 0; z < width - 256 * (ratio - 1); z++) {
                                            ((uint32*)tmptex)[offset + z] = ((uint32*)tex)[x * 256 + y * width + z];
                                        }
                                        for (; z < 256; z++) {
                                            ((uint32*)tmptex)[offset + z] = ((uint32*)tmptex)[offset + z - 1];
                                        }
                                    }
                                    offset += 256;
                                }
                            }
                            free(tex);
                            tex = tmptex;
                            untiled_width = width;
                            untiled_height = height;
                            width = 256;
                            height *= ratio;
                            DBG_INFO(80, "Tiled: %d x %d -> %d x %d\n", untiled_width, untiled_height, width, height);
                        }
                    }
                }

#else  // TEXTURE_TILING

                // Minification
                if (width > _maxwidth || height > _maxheight) {
                    int ratio = 1;
                    if (width / _maxwidth > height / _maxheight) {
                        ratio = (int)ceil((double)width / _maxwidth);
                    }
                    else {
                        ratio = (int)ceil((double)height / _maxheight);
                    }
                    if (!_txReSample->minify(&tex, &width, &height, ratio)) {
                        free(tex);
                        tex = nullptr;
                        DBG_INFO(80, "Error: Minification failed!\n");
                        continue;
                    }
                }

#endif // TEXTURE_TILING

                // Texture compression
                if ((_options & COMPRESSION_MASK) &&
                    (width >= 64 && height >= 64) /* Texture compression is not suitable for low pixel coarse detail
                                                   * textures. The assumption here is that textures larger than 64x64
                                                   * have enough detail to produce decent quality when compressed. The
                                                   * down side is that narrow stripped textures that the N64 often uses
                                                   * for large background textures are also ignored. It would be more
                                                   * reasonable if decisions are made based on Fourier-transform
                                                   * spectrum or RMS error.
                                                   *
                                                   * NOTE: Texture size must be checked before expanding to pow2 size.
                                                   */
                    ) {
                    int dataSize = 0;
                    int compressionType = _options & COMPRESSION_MASK;

#if POW2_TEXTURES
#if (POW2_TEXTURES == 2)
                    // 3DFX Glide3x aspect ratio (8:1 - 1:8)
                    if (!_txReSample->nextPow2(&tex, &width, &height, 32, 1)) {
#else
                    // Normal pow2 expansion
                    if (!_txReSample->nextPow2(&tex, &width, &height, 32, 0)) {
#endif
                        free(tex);
                        tex = nullptr;
                        DBG_INFO(80, "Error: Aspect ratio adjustment failed!\n");
                        continue;
                    }
#endif

                    switch (_options & COMPRESSION_MASK) {
                    case S3TC_COMPRESSION:
                        switch (destformat) {
                        case GFX_TEXFMT_ARGB_8888:
#if GLIDE64_DXTN
                        case GFX_TEXFMT_ARGB_1555: // For ARGB1555 use DXT5 instead of DXT1
#endif
                        case GFX_TEXFMT_ALPHA_INTENSITY_88:
                            dataSize = width * height;
                            break;
#if !GLIDE64_DXTN
                        case GFX_TEXFMT_ARGB_1555:
#endif
                        case GFX_TEXFMT_RGB_565:
                        case GFX_TEXFMT_INTENSITY_8:
                            dataSize = (width * height) >> 1;
                            break;
                        case GFX_TEXFMT_ALPHA_8: // No size benefit with dxtn
                            ;
                        }
                        break;
                    case FXT1_COMPRESSION:
                        switch (destformat) {
                        case GFX_TEXFMT_ARGB_1555:
                        case GFX_TEXFMT_RGB_565:
                        case GFX_TEXFMT_INTENSITY_8:
                            dataSize = (width * height) >> 1;
                            break;
                            // Textures that use 8-bit alpha channel look bad with the current
                            // fxt1 library, so we substitute it with dxtn for now. AFAIK all graphics
                            // cards that support fxt1 also support dxtn. (3DFX and Intel)
                        case GFX_TEXFMT_ALPHA_INTENSITY_88:
                        case GFX_TEXFMT_ARGB_8888:
                            compressionType = S3TC_COMPRESSION;
                            dataSize = width * height;
                            break;
                        case GFX_TEXFMT_ALPHA_8: // No size benefit with dxtn
                            ;
                        }
                        }
                    // Compress it!
                    if (dataSize) {
#if 0 /* TEST: dither before compression for better results with gradients */
                        tmptex = (uint8 *)malloc(_txUtil->sizeofTx(width, height, destformat));
                        if (tmptex) {
                            if (_txQuantize->quantize(tex, tmptex, width, height, GFX_TEXFMT_ARGB_8888, destformat, 0))
                                _txQuantize->quantize(tmptex, tex, width, height, destformat, GFX_TEXFMT_ARGB_8888, 0);
                            free(tmptex);
                        }
#endif
                        tmptex = (uint8 *)malloc(dataSize);
                        if (tmptex) {
                            if (_txQuantize->compress(tex, tmptex,
                                width, height, destformat,
                                &tmpwidth, &tmpheight, &tmpformat,
                                compressionType)) {
                                free(tex);
                                tex = tmptex;
                                width = tmpwidth;
                                height = tmpheight;
                                format = destformat = tmpformat;
                    }
                            else {
                                free(tmptex);
                            }
                    }
                    }
                    }
                else {
#if POW2_TEXTURES
#if (POW2_TEXTURES == 2)
                    // 3DFX Glide3x aspect ratio (8:1 - 1:8)
                    if (!_txReSample->nextPow2(&tex, &width, &height, 32, 1)) {
#else
                    // Normal pow2 expansion
                    if (!_txReSample->nextPow2(&tex, &width, &height, 32, 0)) {
#endif
                        free(tex);
                        tex = nullptr;
                        DBG_INFO(80, "Error: Aspect ratio adjustment failed!\n");
                        continue;
                    }
#endif
                    }

                // Quantize
                {
                    tmptex = (uint8 *)malloc(_txUtil->sizeofTx(width, height, destformat));
                    if (tmptex) {
                        switch (destformat) {
                        case GFX_TEXFMT_ARGB_8888:
                        case GFX_TEXFMT_ARGB_4444:
#if !REDUCE_TEXTURE_FOOTPRINT
                            if (_maxbpp < 32 || _options & FORCE16BPP_HIRESTEX)
#endif
                                destformat = GFX_TEXFMT_ARGB_4444;
                            break;
                        case GFX_TEXFMT_ARGB_1555:
#if !REDUCE_TEXTURE_FOOTPRINT
                            if (_maxbpp < 32 || _options & FORCE16BPP_HIRESTEX)
#endif
                                destformat = GFX_TEXFMT_ARGB_1555;
                            break;
                        case GFX_TEXFMT_RGB_565:
#if !REDUCE_TEXTURE_FOOTPRINT
                            if (_maxbpp < 32 || _options & FORCE16BPP_HIRESTEX)
#endif
                                destformat = GFX_TEXFMT_RGB_565;
                            break;
                        case GFX_TEXFMT_ALPHA_INTENSITY_88:
                        case GFX_TEXFMT_ALPHA_INTENSITY_44:
#if !REDUCE_TEXTURE_FOOTPRINT
                            destformat = GFX_TEXFMT_ALPHA_INTENSITY_88;
#else
                            destformat = GFX_TEXFMT_ALPHA_INTENSITY_44;
#endif
                            break;
                        case GFX_TEXFMT_ALPHA_8:
                            destformat = GFX_TEXFMT_ALPHA_8; // Yes, this is correct. ALPHA_8 instead of INTENSITY_8
                            break;
                        case GFX_TEXFMT_INTENSITY_8:
                            destformat = GFX_TEXFMT_INTENSITY_8;
                        }
                        if (_txQuantize->quantize(tex, tmptex, width, height, GFX_TEXFMT_ARGB_8888, destformat, 0)) {
                            format = destformat;
                            free(tex);
                            tex = tmptex;
                        }
                        }
                    }
                }

            // Last minute validations
            if (!tex || !chksum || !width || !height || !format || width > _maxwidth || height > _maxheight) {
#if !DEBUG
                INFO(80, "-----\n");
                INFO(80, "Path: %ls\n", stdstr(dir_path).ToUTF16().c_str());
                INFO(80, "File: %ls\n", TextureDir.GetNameExtension().ToUTF16().c_str());
#endif
                if (tex) {
                    free(tex);
                    tex = nullptr;
                    INFO(80, "Error: Bad format or size! %d x %d gfmt:%x\n", width, height, format);
                }
                else {
                    INFO(80, "Error: Load failed!!\n");
                }
                continue;
            }

            // Load it into high resolution texture cache
            {
                uint64_t chksum64 = (uint64_t)palchksum;
                chksum64 <<= 32;
                chksum64 |= (uint64_t)chksum;

                GHQTexInfo tmpInfo;
                memset(&tmpInfo, 0, sizeof(GHQTexInfo));

                tmpInfo.data = tex;
                tmpInfo.width = width;
                tmpInfo.height = height;
                tmpInfo.format = format;
                tmpInfo.largeLodLog2 = _txUtil->grLodLog2(width, height);
                tmpInfo.smallLodLog2 = tmpInfo.largeLodLog2;
                tmpInfo.aspectRatioLog2 = _txUtil->grAspectRatioLog2(width, height);
                tmpInfo.is_hires_tex = 1;

#if TEXTURE_TILING
                // Glide64 style texture tiling
                if (untiled_width && untiled_height)
                {
                    tmpInfo.tiles = ((untiled_width - 1) >> 8) + 1;
                    tmpInfo.untiled_width = untiled_width;
                    tmpInfo.untiled_height = untiled_height;
                }
#endif

                // Remove redundant in cache
                if (replace && TxCache::del(chksum64))
                {
                    DBG_INFO(80, "Removed duplicate old cache.\n");
                }

                // Add to cache
                if (TxCache::add(chksum64, &tmpInfo))
                {
                    // Callback to display high resolution texture info
                    // Gonetz
                    if (_callback)
                    {
                        wchar_t tmpbuf[260];
                        mbstowcs(tmpbuf, fname, 260);
                        (*_callback)("[%d] total mem:%.2fmb - %ls\n", _cache.size(), (float)_totalSize / 1000000, tmpbuf);
                    }
                    DBG_INFO(80, "Texture loaded!\n");
                }
                free(tex);
            }
                    } while (TextureDir.FindNext());
                    }
#endif
    return 1;
                }